package com.yuzhihao.writejvm.attributes;

import com.sun.org.apache.bcel.internal.classfile.ConstantMethodHandle;
import com.yuzhihao.writejvm.clazz.ClassInfo;
import com.yuzhihao.writejvm.constant.ConstantMethodHandleInfo;
import com.yuzhihao.writejvm.constant.ConstantPool;
import com.yuzhihao.writejvm.format.PrintFormat;
import com.yuzhihao.writejvm.read.Parse;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.log4j.Log4j2;

import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * The BootstrapMethods attribute is a variable-length attribute in the attributes table of a ClassFile structure (§4.1). The BootstrapMethods attribute records bootstrap method specifiers referenced by invokedynamic instructions (§invokedynamic).
 * <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.23">BootstrapMethodsAttribute</a>
 *
 * @author yzh
 * @since 2021年12月11日18:15:35
 */
@Data
@Log4j2
@EqualsAndHashCode(callSuper = true)
public class BootstrapMethodsAttribute extends Attributes {

    private int num_bootstrap_methods_u2 = 2;

    private int num_bootstrap_methods;

    private List<BootstrapMethods> bms = new ArrayList<>();

    public BootstrapMethodsAttribute(String name){
        super();
        super.setAttribute_name(name);
    }

    @Override
    public void parse(DataInputStream dis, ClassInfo ci) {
        try {
            super.attribute_length = Parse.byteToInt(super.attribute_length_u4, dis);
            this.num_bootstrap_methods = Parse.byteToInt(this.num_bootstrap_methods_u2, dis);

            int i = 0;
            while (i < this.num_bootstrap_methods){
                BootstrapMethods bm = new BootstrapMethods();
                bm.parse(dis, ci);
                this.bms.add(bm);
                i++;
            }
        } catch (IOException e) {
            log.error(e);
        }
    }

    @Override
    public String print(String print) {
        StringBuilder builder = new StringBuilder();
        builder.append("BootstrapMethods").append(PrintFormat.COLON).append(System.lineSeparator());

        for (int i = 0; i < this.bms.size(); i++) {
            BootstrapMethods bm = this.bms.get(i);
            builder.append(bm.print(PrintFormat.EMPTY,i));
        }

        return builder.toString();
    }

    @Data
    @Log4j2
    public static class BootstrapMethods implements Parse{

        private int bootstrap_method_ref_u2 = 2;
        private int bootstrap_method_ref;

        private int num_bootstrap_arguments_u2 = 2;
        private int num_bootstrap_arguments;

        private int bootstrap_arguments_u2 = 2;
        /**
         * Each entry in the bootstrap_arguments array must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_String_info, CONSTANT_Class_info, CONSTANT_Integer_info, CONSTANT_Long_info, CONSTANT_Float_info, CONSTANT_Double_info, CONSTANT_MethodHandle_info, or CONSTANT_MethodType_info structure (§4.4.3, §4.4.1, §4.4.4, §4.4.5, §4.4.8, §4.4.9).
         */
        private List<Integer> indexs = new ArrayList<>();

        private ClassInfo ci;

        private String constant;

        @Override
        public void parse(DataInputStream dis, ClassInfo ci) {
            this.setCi(ci);
            try {
                this.bootstrap_method_ref = Parse.byteToInt(this.bootstrap_method_ref_u2, dis);
                this.num_bootstrap_arguments = Parse.byteToInt(this.num_bootstrap_arguments_u2, dis);

                int i = 0;
                while (i < this.num_bootstrap_arguments){
                    int index = Parse.byteToInt(this.bootstrap_arguments_u2, dis);
                    this.indexs.add(index);
                    i++;
                }
            } catch (IOException e) {
                log.error(e);
            }
        }

        public String print(String print,int index){
            StringBuilder builder = new StringBuilder();
            builder.append(print)
                    .append(index)
                    .append(PrintFormat.COLON)
                    .append(PrintFormat.EMPTY)
                    .append(PrintFormat.HASHTAG)
                    .append(this.bootstrap_method_ref)
                    .append(PrintFormat.EMPTY)
                    .append(this.getConstant())
                    .append(System.lineSeparator())
            ;
            String format = print+PrintFormat.EMPTY;
            builder.append(format).append("Method arguments").append(PrintFormat.COLON).append(System.lineSeparator());
            format += PrintFormat.EMPTY;
            for (Integer i : this.indexs) {
                builder.append(format).append(PrintFormat.HASHTAG).append(i).append(PrintFormat.EMPTY);
                ConstantPool cp = this.getCi().getCp_info().getPools().get(String.valueOf(i));
                if(cp instanceof ConstantMethodHandleInfo){
                    ConstantMethodHandleInfo cmh = (ConstantMethodHandleInfo)cp;
                    builder.append(cmh.getHandle().getDesc()).append(PrintFormat.EMPTY).append(cmh.getConstant());
                }else{
                    builder.append(cp.getConstant());
                }
                builder.append(System.lineSeparator());
            }

            return builder.toString();
        }

        public String getConstant() {
            if(Objects.nonNull(this.constant)){
                return this.constant;
            }

            ConstantMethodHandleInfo cmh = (ConstantMethodHandleInfo)this.getCi().getCp_info().getPools().get(String.valueOf(this.bootstrap_method_ref));
            this.constant = cmh.getHandle().getDesc()+PrintFormat.EMPTY+ cmh.getConstant();

            return constant;
        }
    }
}
